#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include "NativeMath.h"

static const uintarray* bitwiseand(const ninteger* result, const ninteger* a, const ninteger* b)
{
	const uintarray* shortest = (a->content->length < b->content->length) ? a->content : b->content;
	const uintarray* longest = (a->content->length < b->content->length) ? b->content : a->content;
	int resultbitindex, i = 0;

	// temporarily mask out extra sign bits
	UINT backup_a = a->content->values[a->content->length - 1];
	UINT backup_b = b->content->values[b->content->length - 1];

	if ((a->width % BASE_TYPE_BITS) != 0)
		a->content->values[a->content->length - 1] &= bitmasks[a->width % BASE_TYPE_BITS];

	if((b->width % BASE_TYPE_BITS) != 0)
		b->content->values[b->content->length - 1] &= bitmasks[b->width % BASE_TYPE_BITS];

	for (; i < MIN(shortest->length, result->content->length); i++)
		result->content->values[i] = shortest->values[i] & longest->values[i];
	
	for (; i < MIN(longest->length, result->content->length); i++)
		result->content->values[i] = 0;

	/* restore sign bits */
	a->content->values[a->content->length - 1] = backup_a;
	b->content->values[b->content->length - 1] = backup_b;

	// handle sign
	resultbitindex = result->width % BASE_TYPE_BITS;
	if (result->issigned)
		signextend(result, result->width - 1);
	else if ((result->width % BASE_TYPE_BITS) != 0)
		result->content->values[result->content->length - 1] &= bitmasks[resultbitindex];

	return result->content;
}

EXPORT void _and(const ninteger* a, const ninteger* b, const ninteger* result)
{
	bitwiseand(result, a, b);
}

static const uintarray* bitwiseior(const ninteger* result, const ninteger* a, const ninteger* b)
{
	const uintarray* shortest = (a->content->length < b->content->length) ? a->content : b->content;
	const uintarray* longest = (a->content->length < b->content->length) ? b->content : a->content;
	int resultbitindex, i = 0;

	// temporarily mask out extra sign bits
	UINT backup_a = a->content->values[a->content->length - 1];
	UINT backup_b = b->content->values[b->content->length - 1];

	if ((a->width % BASE_TYPE_BITS) != 0)
		a->content->values[a->content->length - 1] &= bitmasks[a->width % BASE_TYPE_BITS];

	if((b->width % BASE_TYPE_BITS) != 0)
		b->content->values[b->content->length - 1] &= bitmasks[b->width % BASE_TYPE_BITS];

	for (; i < MIN(shortest->length, result->content->length); i++)
		result->content->values[i] = shortest->values[i] | longest->values[i];
	
	for (; i < MIN(longest->length, result->content->length); i++)
		result->content->values[i] = longest->values[i];

	/* restore sign bits */
	a->content->values[a->content->length - 1] = backup_a;
	b->content->values[b->content->length - 1] = backup_b;

	// handle sign
	resultbitindex = result->width % BASE_TYPE_BITS;
	if (result->issigned)
		signextend(result, result->width - 1);
	else if ((result->width % BASE_TYPE_BITS) != 0)
		result->content->values[result->content->length - 1] &= bitmasks[resultbitindex];

	return result->content;
}

EXPORT void ior(const ninteger* a, const ninteger* b, const ninteger* result)
{
	bitwiseior(result, a, b);
}

static const uintarray* bitwisexor(const ninteger* result, const ninteger* a, const ninteger* b)
{
	const uintarray* shortest = (a->content->length < b->content->length) ? a->content : b->content;
	const uintarray* longest = (a->content->length < b->content->length) ? b->content : a->content;
	int resultbitindex, i = 0;

	// temporarily mask out extra sign bits
	UINT backup_a = a->content->values[a->content->length - 1];
	UINT backup_b = b->content->values[b->content->length - 1];

	if ((a->width % BASE_TYPE_BITS) != 0)
		a->content->values[a->content->length - 1] &= bitmasks[a->width % BASE_TYPE_BITS];

	if((b->width % BASE_TYPE_BITS) != 0)
		b->content->values[b->content->length - 1] &= bitmasks[b->width % BASE_TYPE_BITS];

	for (; i < MIN(shortest->length, result->content->length); i++)
		result->content->values[i] = shortest->values[i] ^ longest->values[i];
	
	for (; i < MIN(longest->length, result->content->length); i++)
		result->content->values[i] = longest->values[i];

	/* restore sign bits */
	a->content->values[a->content->length - 1] = backup_a;
	b->content->values[b->content->length - 1] = backup_b;

	// handle sign
	resultbitindex = result->width % BASE_TYPE_BITS;
	if (result->issigned)
		signextend(result, result->width - 1);
	else if ((result->width % BASE_TYPE_BITS) != 0)
		result->content->values[result->content->length - 1] &= bitmasks[resultbitindex];

	return result->content;
}

EXPORT void _xor(const ninteger* a, const ninteger* b, const ninteger* result)
{
	bitwisexor(result, a, b);
}

EXPORT void _not(const ninteger* a, const ninteger* result)
{
	int i;
	int resultbitindex = result->width % BASE_TYPE_BITS;

	for (i = 0; i < MIN(result->content->length, a->content->length); i++)
		result->content->values[i] = ~(a->content->values[i]);


	if (result->issigned)
		signextend(result, result->width - 1);
	else
		if ((result->width % BASE_TYPE_BITS) != 0)
			result->content->values[result->content->length - 1] &= bitmasks[resultbitindex];
}

static void leftshift(const uintarray* result, const ninteger* a, const UINT value)
{
	/* number of words to shift */
	UINT ignore = value / BASE_TYPE_BITS;

	/* bits to shift */
	UINT shiftValue = value % BASE_TYPE_BITS;

	UINT temp = 0;
	int i = 0;

	/* special cases */
	if(value == 0)
	{
		/* result equals input */
		memcpy(result->values, a->content->values, result->length * sizeof(UINT));
		return;
	} else if(value > result->length * BASE_TYPE_BITS)
	{
		/* all zeros */
		return;
	}

	while ((i < (result->length - ignore)) && (i < a->content->length))
	{
		ULONG t = (ULONG)a->content->values[i] << (int)shiftValue;       
		result->values[ignore + i] = (UINT)t | temp;
		temp = (UINT)(t >> BASE_TYPE_BITS);
		i++;
	}

	if (i < (result->length - ignore) && (i == a->content->length))
		result->values[ignore + i] = temp;
}

EXPORT void shl(const ninteger* a, const int value, const ninteger* result)
{
	if(value < 0)
	{
		perror("can't shift a negative number of places");
	} 

	leftshift(result->content, a, (UINT)value);

	if(result->issigned)
		signextend(result, result->width - 1);
	else
	{
		if((result->width % BASE_TYPE_BITS) != 0)
		{
			result->content->values[result->content->length - 1] &= bitmasks[result->width % BASE_TYPE_BITS];
		}
	}
}

EXPORT void shr(const ninteger* a, const int value, const ninteger* result)
{
	if(value < 0)
	{
		perror("shift value cannot be negative");
	}
	else if(value == 0)
	{
		valuecopy(a, result);
		return;
	}
	else if(value > a->width)
	{
		// result will be all zeros
		memset(result->content->values, 0, result->content->length * sizeof(UINT));
		return;
	} else
	{
		// Number of (whole) words (size BASE_TYPE_BITS) to shift
		int wordsToShift = value / BASE_TYPE_BITS;

		// Number of bits to shift (between 0 and BASE_TYPE_BITS)
		int bitsToShift = value % BASE_TYPE_BITS;


		int min, index, i;
		ULONG t;
		UINT temp;

		// fill with sign bits ..
		if (a->issigned && isnegative(a))
		{
			// fill with sign bits
			for (i = result->content->length - 1; i >= a->content->length - wordsToShift; i--)
				result->content->values[i] = BASE_MAX_VALUE;
		}
		else
		{
			// fill with zeros
			for (i = result->content->length - 1; i >= a->content->length - wordsToShift; i--)
				result->content->values[i] = 0;
		}

		// first, shift 'wordsToShift' times words to the right	
		// placing the result in 'result'
		min = MIN(result->content->length, a->content->length);
		for (i = min - 1 - wordsToShift; i >= 0; i--)
			result->content->values[i] = a->content->values[i + wordsToShift];

		// second, shift 'bitsToShift' to the right ..
		t = 0;
		temp = (a->issigned && isnegative(a)) ? BASE_MAX_VALUE : 0;
		index = result->content->length - 1;

		do
		{
			t = ((ULONG)result->content->values[index] << BASE_TYPE_BITS) >> bitsToShift;
			result->content->values[index] = (UINT)(t >> BASE_TYPE_BITS) | temp;
			temp = (UINT)t;
		} while (--index >= 0);

		// sign extend if needed
		if (result->issigned)
			signextend(result, a->width - 1); // possible bug here?

		// done
	}	
}